/*
 * JFugue - API for Music Programming
 * Copyright (C) 2003-2008  David Koelle
 *
 * http://www.jfugue.org 
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *  
 */

package org.jfugue;

import javax.sound.midi.Sequence;
import javax.sound.midi.ShortMessage;

/**
 * This class takes a Pattern, and turns it into wonderful music.
 *
 * <p>
 * Playing music is only one thing that can be done by rendering a pattern. You
 * could also create your own renderer that draws sheet music based on a
 * pattern. Or, you could create a graphical light show based on the musical
 * notes in the pattern.
 * </p>
 * 
 * <p>
 * This was named Renderer in previous versions of JFugue. The name has been
 * changed to differentiate it from other types of renderers.
 * </p>
 *
 * @author David Koelle
 * @version 2.0
 * @version 3.0 - Renderer renamed to MidiRenderer
 */
public final class MidiRenderer extends ParserListenerAdapter {
	private MidiEventManager eventManager;
	long initialNoteTime = 0;
	private float sequenceTiming;
	private int resolution;

	/**
	 * Instantiates a Renderer
	 */
	public MidiRenderer(float sequenceTiming, int resolution) {
		reset(sequenceTiming, resolution);
	}

	/**
	 * Creates a new MidiEventManager. If this isn't called, events from
	 * multiple calls to render() will be added to the same eventManager, which
	 * means that the second time render() is called, it will contain music left
	 * over from the first time it was called. (This wasn't a problem with Java
	 * 1.4)
	 * 
	 * @since 3.0
	 */
	public void reset(float sequenceTiming, int resolution) {
		this.sequenceTiming = sequenceTiming;
		this.resolution = resolution;
		this.eventManager = new MidiEventManager(sequenceTiming, resolution);
	}

	/**
	 * Creates a new MidiEventManager using the sequenceTiming and resolution
	 * already used to create this MidiRenderer. If this isn't called, events
	 * from multiple calls to render() will be added to the same eventManager,
	 * which means that the second time render() is called, it will contain
	 * music left over from the first time it was called. (This wasn't a problem
	 * with Java 1.4)
	 * 
	 * @since 3.2
	 */
	public void reset() {
		this.eventManager = new MidiEventManager(this.sequenceTiming,
				this.resolution);
	}

	/**
	 * Returns the last sequence generated by this renderer
	 */
	public Sequence getSequence() {
		return this.eventManager.getSequence();
	}

	// ParserListener methods
	////////////////////////////

	@Override
	public void voiceEvent(Voice voice) {
		this.eventManager.setCurrentTrack(voice.getVoice());
	}

	@Override
	public void tempoEvent(Tempo tempo) {
		byte[] threeTempoBytes = TimeFactor
				.convertToThreeTempoBytes(tempo.getTempo());
		this.eventManager.addMetaMessage(0x51, threeTempoBytes);
	}

	@Override
	public void instrumentEvent(Instrument instrument) {
		this.eventManager.addEvent(ShortMessage.PROGRAM_CHANGE,
				instrument.getInstrument(), 0);
	}

	@Override
	public void layerEvent(Layer layer) {
		this.eventManager.setCurrentLayer(layer.getLayer());
	}

	@Override
	public void timeEvent(Time time) {
		this.eventManager.setTrackTimer(time.getTime());
	}

	@Override
	public void measureEvent(Measure measure) {
		// No MIDI is generated when a measure indicator is identified.
	}

	@Override
	public void keySignatureEvent(KeySignature keySig) {
		this.eventManager.addMetaMessage(0x59,
				new byte[] { keySig.getKeySig(), keySig.getScale() });
	}

	@Override
	public void controllerEvent(Controller controller) {
		this.eventManager.addEvent(ShortMessage.CONTROL_CHANGE,
				controller.getIndex(), controller.getValue());
	}

	@Override
	public void channelPressureEvent(ChannelPressure channelPressure) {
		this.eventManager.addEvent(ShortMessage.CHANNEL_PRESSURE,
				channelPressure.getPressure());
	}

	@Override
	public void polyphonicPressureEvent(PolyphonicPressure polyphonicPressure) {
		this.eventManager.addEvent(ShortMessage.POLY_PRESSURE,
				polyphonicPressure.getKey(), polyphonicPressure.getPressure());
	}

	@Override
	public void pitchBendEvent(PitchBend pitchBend) {
		this.eventManager.addEvent(ShortMessage.PITCH_BEND,
				pitchBend.getBend()[0], pitchBend.getBend()[1]);
	}

	@Override
	public void noteEvent(Note note) {
		// Remember the current track time, so we can flip back to it
		// if there are other notes to play in parallel
		this.initialNoteTime = this.eventManager.getTrackTimer();
		long duration = note.getDuration();

		// If there is no duration, don't add this note to the event manager
		// TODO: This is a special case as of v4.0.3 that should be re-thought
		// if a new noteEvent callback is created in v5.0
		if (duration == 0) {
			return;
		}

		// Add messages to the track
		if (note.isRest()) {
			this.eventManager.advanceTrackTimer(duration);
		} else {
			initialNoteTime = eventManager.getTrackTimer();
			byte attackVelocity = note.getAttackVelocity();
			byte decayVelocity = note.getDecayVelocity();
			this.eventManager.addNoteEvent(note.getValue(), attackVelocity,
					decayVelocity, duration, !note.isEndOfTie(),
					!note.isStartOfTie());
		}
	}

	@Override
	public void sequentialNoteEvent(Note note) {
		long duration = note.getDuration();
		if (note.isRest()) {
			this.eventManager.advanceTrackTimer(duration);
		} else {
			byte attackVelocity = note.getAttackVelocity();
			byte decayVelocity = note.getDecayVelocity();
			this.eventManager.addNoteEvent(note.getValue(), attackVelocity,
					decayVelocity, duration, !note.isEndOfTie(),
					!note.isStartOfTie());
		}
	}

	@Override
	public void parallelNoteEvent(Note note) {
		long duration = note.getDuration();
		this.eventManager.setTrackTimer(this.initialNoteTime);
		if (note.isRest()) {
			this.eventManager.advanceTrackTimer(duration);
		} else {
			byte attackVelocity = note.getAttackVelocity();
			byte decayVelocity = note.getDecayVelocity();
			this.eventManager.addNoteEvent(note.getValue(), attackVelocity,
					decayVelocity, duration, !note.isEndOfTie(),
					!note.isStartOfTie());
		}
	}
}
